package de.tu_darmstadt.informatik.tk.lernsystem.servlet;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import de.tu_darmstadt.informatik.tk.lernsystem.concepts.Annotation;
import de.tu_darmstadt.informatik.tk.lernsystem.concepts.Note;
import de.tu_darmstadt.informatik.tk.lernsystem.concepts.Reference;
import de.tu_darmstadt.informatik.tk.lernsystem.concepts.Section;
import de.tu_darmstadt.informatik.tk.lernsystem.concepts.ShareRequest;
import de.tu_darmstadt.informatik.tk.lernsystem.concepts.Tag;
import de.tu_darmstadt.informatik.tk.lernsystem.concepts.TagAnnotation;
import de.tu_darmstadt.informatik.tk.lernsystem.concepts.Thing;
import de.tu_darmstadt.informatik.tk.lernsystem.concepts.User;
import de.tu_darmstadt.informatik.tk.lernsystem.concepts.Website;
import de.tu_darmstadt.informatik.tk.lernsystem.concepts.WebsiteSection;
import de.tu_darmstadt.informatik.tk.lernsystem.dao.UriDao;
import de.tu_darmstadt.informatik.tk.lernsystem.dao.UserDao;
import de.tu_darmstadt.informatik.tk.lernsystem.util.ConceptHelper;

/**
 * This class handled incoming reference manager requests. 
 */
public class ReferenceServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
	private HttpSession httpSession = null;
	
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doGet(request, response);
	}
	
	/**
	 * Request parsing.
	 * By inspecingg the GET-Parameter "action", the method
	 * decides what to do.
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String action = request.getParameter("action");
		this.httpSession=request.getSession();
		if (action == null || action.isEmpty()) {
			return;
		}
		
		if (action.equals("expectQueuedReferencesUpdate")) {
			expectQueuedReferencesUpdate(response);
			return;
		}
		
		try {
			JSONObject json = null;
			if (action.equals("queueReference")) {
				json = queueReference(request.getParameter("uri"), request.getParameter("xpath"), request.getParameter("token"));
				
				String callback = request.getParameter("callback");
				response.setContentType("text/javascript");
				PrintWriter out = response.getWriter();
				out.print(callback+"("+json.toString()+");");
				out.flush();
				out.close();
			} else {
				if (action.equals("getReferences")) {
					json = getReferences(request.getParameter("baseUri"));
				} else if (action.equals("addReferenceWeb2Web")) {
					json = addReferenceWeb2Web(request.getParameter("baseUri"), request.getParameter("baseXpath"), request.getParameter("targetUri"), request.getParameter("targetXpath"));
				} else if (action.equals("getQueuedReferences")) {
					json = getQueuedReferences();
				} else if (action.equals("discardQueuedReference")) {
					json = discardQueuedReference(request.getParameter("uri"), request.getParameter("xpath"));
				} else if (action.equals("setRating")) {
					json = setRating(request.getParameter("uri"), Integer.valueOf(request.getParameter("rating")));
				} else if (action.equals("setSemanticRelation")) {
					json = setSemanticRelation(request.getParameter("refId"), request.getParameter("semanticRelation"));
				} else if (action.equals("shareReference")) {
					json = shareReference(request.getParameter("refId"), request.getParameter("username"));
				} else if (action.equals("getShareRequests")) {
					json = getShareRequests();
				} else if (action.equals("respondToShareRequests")) {
					json = respondToShareRequests(request.getParameter("shareRequestId"), Boolean.valueOf(request.getParameter("accept")));
				} else if (action.equals("addNote")) {
					json = addNote(request.getParameter("uri"), request.getParameter("xpath"), request.getParameter("text"));
				} else if (action.equals("getNotes")) {
					json = getNotes(request.getParameter("uri"));
				} else if (action.equals("deleteAnnotation")) {
					json = deleteAnnotation(request.getParameter("id"));
				} else if (action.equals("addTag")) {
					json = addTag(request.getParameter("uri"), request.getParameter("xpath"), request.getParameter("tag"));
				} else if (action.equals("getTags")) {
					json = getTags(request.getParameter("uri"));
				} else if (action.equals("removeTag")) {
					json = removeTag(request.getParameter("uri"), request.getParameter("xpath"), request.getParameter("tag"));
				} else if (action.equals("getUserList")) {
					json = getUserList();
				} else if (action.equals("getReferencesWeb")) {
					json = getReferencesWeb(request.getParameter("uri"), request.getParameter("xpath"), request.getParameter("token"));
				}
				
				if (json != null) {
					response.setContentType("application/json");
					PrintWriter out = response.getWriter();
					out.print(json.toString());
					out.flush();
					out.close();
				}
			}
		} catch (JSONException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Return list of all users
	 * @return JSON
	 * @throws JSONException
	 */
	public JSONObject getUserList() throws JSONException {
		List<User> users = UserDao.getAll();
		
		JSONObject result = new JSONObject();
		JSONArray uriList = new JSONArray();
		for (User user : users) {
			JSONObject item = new JSONObject();
			item.put("id", user.toString());
			item.put("username", user.getUsername());
			uriList.put(item);
		}
		result.put("results", uriList.length());
		result.put("rows", uriList);
		return result;
	}
	
	/**
	 * Return list of all tags of a URI
	 * @param uri
	 * @return JSON
	 * @throws JSONException
	 */
	public JSONObject getTags(String uri) throws JSONException {
		Thing thing = UriDao.getThing(uri);
		List<Annotation> list = UriDao.getAnnotations(thing, getSessionUser());
		
		JSONObject result = new JSONObject();
		JSONArray uriList = new JSONArray();
		for (Annotation a : list) {
			if (a instanceof TagAnnotation) {
				JSONObject item = new JSONObject();
				item.put("id", a.toString());
				item.put("tag", ((TagAnnotation) a).getTag().getTag());
				if (a.getPartOf() instanceof WebsiteSection) {
					WebsiteSection ws = (WebsiteSection)a.getPartOf();
					item.put("xpath", ws.getXpath());
				}
				uriList.put(item);
			}
		}
		result.put("results", uriList.length());
		result.put("rows", uriList);
		return result;
	}
	
	/**
	 * Add tag to a URI/XPath
	 * @param uri
	 * @param xpath
	 * @param text
	 * @return JSON. True if successful
	 * @throws JSONException
	 */
	public JSONObject addTag(String uri, String xpath, String text) throws JSONException {
		WebsiteSection section = UriDao.factorWebsiteSection(uri, xpath);
		Tag tag = UriDao.getTag(text);
		UriDao.addTag(section, tag, getSessionUser());
		
		JSONObject result = new JSONObject();
		result.put("success", true);
		return result;
	}
	
	/**
	 * Remove tag from URI/XPath
	 * @param uri
	 * @param xpath
	 * @param text
	 * @return JSON. True if successful
	 * @throws JSONException
	 */
	public JSONObject removeTag(String uri, String xpath, String text) throws JSONException {
		WebsiteSection section = UriDao.factorWebsiteSection(uri, xpath);
		Tag tag = UriDao.getTag(text);
		for (Annotation a : section.getAnnotations()) {
			if (a instanceof TagAnnotation) {
				if (tag.equals(((TagAnnotation) a).getTag())) {
					UriDao.deleteAnnotation((TagAnnotation)a);
				}
			}
		}
		
		JSONObject result = new JSONObject();
		result.put("success", true);
		return result;
	}
	
	/**
	 * Adds a note to a URI/XPath
	 * @param uri
	 * @param xpath
	 * @param text
	 * @return JSON. True if successful
	 * @throws JSONException
	 */
	public JSONObject addNote(String uri, String xpath, String text) throws JSONException {
		WebsiteSection section = UriDao.factorWebsiteSection(uri, xpath);
		UriDao.addNote(section, text, getSessionUser());
		
		JSONObject result = new JSONObject();
		result.put("success", true);
		return result;
	}
	
	/**
	 * Removes an annotation from a resource.
	 * @param id ID of annotation
	 * @return JSON. True if successful
	 * @throws JSONException
	 */
	public JSONObject deleteAnnotation(String id) throws JSONException {
		Annotation a = UriDao.getAnnotation(id);
		UriDao.deleteAnnotation(a);
		
		JSONObject result = new JSONObject();
		result.put("success", true);
		return result;
	}
	
	/**
	 * Return list of all notes of a URI
	 * @param uri
	 * @return JSON
	 * @throws JSONException
	 */
	public JSONObject getNotes(String uri) throws JSONException {
		Thing thing = UriDao.getThing(uri);
		List<Annotation> list = UriDao.getAnnotations(thing, getSessionUser());
		
		JSONObject result = new JSONObject();
		JSONArray uriList = new JSONArray();
		for (Annotation a : list) {
			if (a instanceof Note) {
				JSONObject item = new JSONObject();
				item.put("id", a.toString());
				item.put("text", ((Note) a).getText());
				if (a.getPartOf() instanceof WebsiteSection) {
					WebsiteSection ws = (WebsiteSection)a.getPartOf();
					item.put("xpath", ws.getXpath());
				}
				uriList.put(item);
			}
		}
		result.put("results", uriList.length());
		result.put("rows", uriList);
		return result;
	}
	
	/**
	 * Sets the rating (range: 0-5)
	 * @param uri
	 * @param rating
	 * @return JSON. True if successful
	 * @throws JSONException
	 */
	public JSONObject setRating(String uri, Integer rating) throws JSONException {
		Thing thing = UriDao.getThing(uri);
		UriDao.setRating(thing, getSessionUser(), rating);
		
		JSONObject result = new JSONObject();
		result.put("success", true);
		return result;
	}
	
	/**
	 * 
	 * @param refId
	 * @param semanticRelation
	 * @return
	 * @throws JSONException
	 */
	private JSONObject setSemanticRelation(String refId, String semanticRelation) throws JSONException {
		JSONObject result = new JSONObject();
		Reference ref = UriDao.getReferenceById(refId);
		if (ref != null) {
			UriDao.setSemanticRelation(ref, semanticRelation);
			result.put("success", true);
		} else {
			result.put("success", false);
		}
		return result;
	}
	
	/**
	 * Acceps/discards an incoming share request
	 * @param shareId
	 * @param accept
	 * @return JSON. True if successful
	 * @throws JSONException
	 */
	public JSONObject respondToShareRequests(String shareId, Boolean accept) throws JSONException {
		UriDao.answerShareRequest(shareId, accept);
		
		JSONObject result = new JSONObject();
		result.put("success", true);
		return result;
	}
	
	/**
	 * Sends a share requests for a specific reference
	 * @param refId
	 * @param userId
	 * @return JSON. True if successful
	 * @throws JSONException
	 */
	public JSONObject shareReference(String refId, String userId) throws JSONException {
		JSONObject result = new JSONObject();

		User user = UserDao.getUserById(userId);
		if (user != null) {
			Reference ref = UriDao.getReferenceById(refId);
			if (ref != null) {
				UriDao.addShareRequest(ref, user);
				result.put("success", true);
			} else {
				result.put("success", false);
			}
		} else {
			result.put("success", false);
		}
		
		return result;
	}
	
	/**
	 * Adds a personal Web2Web-Reference
	 * @param baseUri
	 * @param baseXpath
	 * @param targetUri
	 * @param targetXpath
	 * @return JSON. True if successful
	 * @throws JSONException
	 */
	public JSONObject addReferenceWeb2Web(String baseUri, String baseXpath, String targetUri, String targetXpath) throws JSONException {
		WebsiteSection base = UriDao.factorWebsiteSection(baseUri, baseXpath);
		WebsiteSection target = UriDao.factorWebsiteSection(targetUri, targetXpath);
		UriDao.addReference(base, target, getSessionUser());
		
		JSONObject result = new JSONObject();
		result.put("success", true);
		return result;
	}

	/**
	 * Discards a queued reference
	 * @param uri
	 * @param xpath
	 * @return JSON. True if successful
	 * @throws JSONException
	 */
	public JSONObject discardQueuedReference(String uri, String xpath) throws JSONException {
		WebsiteSection section = UriDao.factorWebsiteSection(uri, xpath);
		UriDao.deleteWebsiteSection(section);
		JSONObject result = new JSONObject();
		result.put("success", true);
		return result;
	}
	
	/**
	 * Return list of incoming personal share requests
	 * @return JSON
	 * @throws JSONException
	 */
	public JSONObject getShareRequests() throws JSONException {
		List<ShareRequest> list = UriDao.getPendingShareRequests(getSessionUser());
		
		JSONObject result = new JSONObject();
		JSONArray uriList = new JSONArray();
		for (ShareRequest sr : list) {
			JSONObject item = new JSONObject();
			item.put("id", sr.toString());
			item.put("ownerId", sr.getAnnotation().getOwner().toString());
			item.put("ownerUsername", sr.getAnnotation().getOwner().getUsername());
			if (sr.getAnnotation() instanceof Reference) {
				Reference ref = (Reference)sr.getAnnotation();
				if (ref.getSourceSection() instanceof WebsiteSection) {
					WebsiteSection ws = (WebsiteSection)ref.getSourceSection();
					item.put("sourceRefType", "websiteSection");
					item.put("sourceUri", ((Website)ws.getPartOf()).toString());
					item.put("sourceXpath", ws.getXpath());
				}
				if (ref.getTargetSection() instanceof WebsiteSection) {
					WebsiteSection ws = (WebsiteSection)ref.getTargetSection();
					item.put("targetRefType", "websiteSection");
					item.put("targetUri", ((Website)ws.getPartOf()).toString());
					item.put("targetXpath", ws.getXpath());
				}
			}
			uriList.put(item);
		}
		result.put("results", uriList.length());
		result.put("rows", uriList);
		return result;
	}
	
	/**
	 * Return list of personal queued reference
	 * @return
	 * @throws JSONException
	 */
	public JSONObject getQueuedReferences() throws JSONException {
		List<Reference> list = UriDao.getQueuedReferences(getSessionUser());
		
		JSONObject result = new JSONObject();
		JSONArray uriList = new JSONArray();
		for (Reference ref : list) {
			if (ref.getSourceSection() instanceof WebsiteSection) {
				WebsiteSection section = (WebsiteSection)ref.getSourceSection();
				JSONObject item = new JSONObject();
				item.put("refuri", section.getPartOf().toString());
				item.put("refxpath", section.getXpath() == null ? "" : section.getXpath());
				uriList.put(item);
			}
		}
		result.put("results", uriList.length());
		result.put("rows", uriList);
		return result;
	}
	
	/**
	 * Returns list of all personal/shared references of a URI
	 * @param uri
	 * @param xpath Currently not used!
	 * @param credentialToken
	 * @return
	 * @throws JSONException
	 */
	public JSONObject getReferencesWeb(String uri, String xpath, String credentialToken) throws JSONException {
		User user = UserDao.getUserByCredentialToken(credentialToken);
		return getReferencesInternal(uri, user);
	}
	
	/**
	 * Queues a URI/Xpath for further processing
	 * @param uri
	 * @param xpath
	 * @param credentialToken
	 * @return JSON. True if successful
	 * @throws JSONException
	 */
	public JSONObject queueReference(String uri, String xpath, String credentialToken) throws JSONException {
		User user = UserDao.getUserByCredentialToken(credentialToken);
		JSONObject result = new JSONObject();
		if (user instanceof User) {
			WebsiteSection base = UriDao.factorWebsiteSection(uri, xpath);
			UriDao.queueReference(base, user);
			/*
			Notifications n = new Notifications();
			try {
				n.connect();
				MapMessage map = n.createMapMessage();
				map.setString("username", user.getUsername());
				map.setString("action", "queueReference");
				n.sendMessage(map);
				n.disconnect();
			} catch (JMSException e) {
				e.printStackTrace();
			}
			*/
			result.put("success", true);
		} else {
			result.put("success", false);
		}
		return result;
	}
	
	/**
	 * Not used. 
	 * @param response
	 * @throws IOException
	 */
	public void expectQueuedReferencesUpdate(HttpServletResponse response) throws IOException {
		try {
			User user = getSessionUser();
			/*
			Notifications n = new Notifications();
			n.connect();
			Message m = n.receive(20000);
			if (m instanceof MapMessage) {
				if (user.getUsername().equals(((MapMessage) m).getString("username"))) {
					response.sendError(200);
					n.disconnect();
					return;
				}
			}
			n.disconnect();
			*/
		} catch (Exception e) {
			e.printStackTrace();
		}
		response.sendError(503);
	}
	
	/**
	 * Internal helper function for generating a JSON-list of personal/shared references
	 * @param uri
	 * @param user
	 * @return
	 * @throws JSONException
	 */
	private JSONObject getReferencesInternal(String uri, User user) throws JSONException {
		Thing base = UriDao.getThing(uri);
		List<Reference> list = UriDao.getReferences(base, user);
		
		JSONObject result = new JSONObject();
		JSONArray uriList = new JSONArray();
		for (Reference ref : list) {
			Section targetSection;
			if (base.equals(ref.getTargetSection().getPartOf())) {
				targetSection = ref.getSourceSection();
			} else {
				targetSection = ref.getTargetSection();
			}
			
			JSONObject item = new JSONObject();
		
			item.put("id", ref.toString());
			item.put("ownerId", ref.getOwner().toString());
			item.put("ownerUsername", ref.getOwner().getUsername());
			item.put("semanticRelation", ref.getSemanticRelation());
			item.put("myRating", ConceptHelper.getMyRating(user, targetSection.getPartOf()));
			item.put("avgRating", ConceptHelper.getAverageRating(user, targetSection.getPartOf()));
			item.put("tags", ConceptHelper.implodeTagList(UriDao.getTags(targetSection, user)));
			if (targetSection instanceof WebsiteSection) {
				WebsiteSection ws = (WebsiteSection)targetSection;
				item.put("targetRefType", "websiteSection");
				item.put("targetUri", ((Website)ws.getPartOf()).toString());
				item.put("targetXpath", ws.getXpath());
			}
			uriList.put(item);
		} 
		result.put("results", uriList.length());
		result.put("rows", uriList); 
		return result;
	}
	
	public JSONObject getReferences(String baseUri) throws JSONException {
		/*//estdata
		 JSONObject item = new JSONObject();
		JSONArray uriList = new JSONArray();
		JSONObject result = new JSONObject();
		item.put("id", 1);
		item.put("ownerId", "test@googlemail.com");
		item.put("ownerUsername", "Max Mustermann");
		item.put("myRating", 3);
		item.put("avgRating", 3);
		item.put("tags", "cue");
		
			item.put("targetRefType", "websiteSection");
			item.put("targetUri", "tu-darmstadt.de");
			item.put("targetXpath", "ddscc");
			uriList.put(item);
			result.put("results", 1);
			result.put("rows", uriList); 
			return result; */
		return getReferencesInternal(baseUri, getSessionUser());
	}
	
	public User getSessionUser() {
		return UserDao.getUserByUsername((String)httpSession.getAttribute("userId"));
	}
}
